﻿//-----------------------------------------------------------------------
// <copyright file="SceneToolBase.cs" company="Nathan Miller">
// Copyright (c) Nathan Miller.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
//-----------------------------------------------------------------------
namespace TileTool.Base.Editor.Tool.Scene
{
    using System;
    using TileTool.Base.Tool;
    using TileTool.Common.Math;
    using TileTool.Editor.Tool.Scene;
    using TileTool.Input;
    using TileTool.Input.Focus;
    using TileTool.Input.Keyboard;
    using TileTool.Input.Mouse;
    using TileTool.Message;
    using TileTool.Render;
    using TileTool.Scene;
    using TileTool.Scene.Entity;
    using TileTool.Scene.Message;
    using TileTool.Tool;

    /// <summary>
    /// Abstract scene tool class to be used as a base for all scene tools.
    /// </summary>
    public abstract class SceneToolBase : ToolBase, ISceneTool
    {
        #region Properties - Public

        /// <summary>
        /// Gets or sets the scene the tool is operating on.
        /// </summary>
        public ISceneManager Scene
        {
            get;
            set;
        }

        #endregion

        #region Properties - Protected

        /// <summary>
        /// Gets the current scene selection.
        /// </summary>
        protected IEntityCollection Selection
        {
            get;
            private set;
        }

        #endregion

        #region Methods - Public

        /// <summary>
        /// Called when a tool is installed into a tool manager. Should be called
        /// by the tool manager after the tool is already in the tool stack.
        /// </summary>
        /// <param name="manager">Tool manager that is hosting the tool. Always non-null.</param>
        public override void Install(IToolManager manager)
        {
            base.Install(manager);

            this.Selection = this.Scene.Selection;

            this.RegisterForMessages();
        }

        /// <summary>
        /// Called when a tool is uninstalled from a tool manager. Should be called
        /// by the tool manager after the tool has already been removed form the tool
        /// stack.
        /// </summary>
        public override void Uninstall()
        {
            this.UnregisterForMessages();

            base.Uninstall();
        }

        /// <summary>
        /// Draw the tool.
        /// </summary>
        /// <param name="renderData">Rendering data. Always non-null.</param>
        public virtual void Draw(IRenderData renderData)
        {
        }

        /// <summary>
        /// Handle input data from a canvas.
        /// </summary>
        /// <param name="sceneInputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        public virtual InputHandled HandleInput(SceneToolInputData sceneInputData)
        {
            InputData inputData = sceneInputData.InputData;

            switch (inputData.InputType)
            {
                case InputDataType.MouseMove:
                    return this.HandleMouseMove(sceneInputData, (MouseMoveInputData)inputData);
                case InputDataType.MouseButton:
                    return this.HandleMouseButton(sceneInputData, (MouseButtonInputData)inputData);
                case InputDataType.Key:
                    return this.HandleKey(sceneInputData, (KeyInputData)inputData);
                case InputDataType.Focus:
                    return this.HandleFocus(sceneInputData, (FocusInputData)inputData);
            }

            return InputHandled.No;
        }

        #endregion

        #region Methods - Protected - Misc

        /// <summary>
        /// Register for any messages we are interested in.
        /// </summary>
        protected virtual void RegisterForMessages()
        {
            IToolManager manager = Manager;
            MessageRouter router = manager.MessageRouter;

            router.RegisterForMessage<SceneSelectionChangedMessage>(
                this.HandleSceneSelectionChangedMessage,
                new SceneMessageFilter(this.Scene));
            router.RegisterForMessage<SceneClearedMessage>(
                this.HandleSceneClearedMessage,
                new SceneMessageFilter(this.Scene));
        }

        /// <summary>
        /// Unregister for any messages we are interested in.
        /// </summary>
        protected virtual void UnregisterForMessages()
        {
            IToolManager manager = Manager;
            MessageRouter router = manager.MessageRouter;

            router.UnregisterForMessage<SceneSelectionChangedMessage>(
                this.HandleSceneSelectionChangedMessage);
        }

        #endregion

        #region Methods - Protected - Scene

        /// <summary>
        /// Called when the tool gets a scene selection changed message.
        /// </summary>
        /// <param name="selection">New selection in the scene.</param>
        /// <param name="oldSelection">Old selection.</param>
        protected virtual void SceneSelectionChanged(IEntityCollection selection, IEntityCollection oldSelection)
        {
        }

        /// <summary>
        /// Called when the scene is cleared.
        /// </summary>
        protected virtual void SceneCleared()
        {
        }

        #endregion

        #region Methods - Protected - Input functions

        /// <summary>
        /// Handle mouse move input data.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected virtual InputHandled HandleMouseMove(SceneToolInputData sceneInputData, MouseMoveInputData inputData)
        {
            return InputHandled.No;
        }

        /// <summary>
        /// Handle mouse button input data.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected virtual InputHandled HandleMouseButton(SceneToolInputData sceneInputData, MouseButtonInputData inputData)
        {
            if (inputData.Down)
            {
                switch (inputData.Button)
                {
                    case MouseButton.Left:
                        return this.HandleLeftMouseButtonDown(sceneInputData, inputData);
                    case MouseButton.Right:
                        return this.HandleRightMouseButtonDown(sceneInputData, inputData);
                    case MouseButton.Middle:
                        return this.HandleMiddleMouseButtonDown(sceneInputData, inputData);
                }
            }
            else
            {
                switch (inputData.Button)
                {
                    case MouseButton.Left:
                        return this.HandleLeftMouseButtonUp(sceneInputData, inputData);
                    case MouseButton.Right:
                        return this.HandleRightMouseButtonUp(sceneInputData, inputData);
                    case MouseButton.Middle:
                        return this.HandleMiddleMouseButtonUp(sceneInputData, inputData);
                }
            }

            return InputHandled.No;
        }

        /// <summary>
        /// Handle left mouse button down.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected virtual InputHandled HandleLeftMouseButtonDown(SceneToolInputData sceneInputData, MouseButtonInputData inputData)
        {
            return InputHandled.No;
        }

        /// <summary>
        /// Handle left mouse button up.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected virtual InputHandled HandleLeftMouseButtonUp(SceneToolInputData sceneInputData, MouseButtonInputData inputData)
        {
            return InputHandled.No;
        }

        /// <summary>
        /// Handle right mouse button down.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected virtual InputHandled HandleRightMouseButtonDown(SceneToolInputData sceneInputData, MouseButtonInputData inputData)
        {
            return InputHandled.No;
        }

        /// <summary>
        /// Handle right mouse button up.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected virtual InputHandled HandleRightMouseButtonUp(SceneToolInputData sceneInputData, MouseButtonInputData inputData)
        {
            return InputHandled.No;
        }

        /// <summary>
        /// Handle middle mouse button down.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected virtual InputHandled HandleMiddleMouseButtonDown(SceneToolInputData sceneInputData, MouseButtonInputData inputData)
        {
            return InputHandled.No;
        }

        /// <summary>
        /// Handle middle mouse button up.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected virtual InputHandled HandleMiddleMouseButtonUp(SceneToolInputData sceneInputData, MouseButtonInputData inputData)
        {
            return InputHandled.No;
        }

        /// <summary>
        /// Handle key input data.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected virtual InputHandled HandleKey(SceneToolInputData sceneInputData, KeyInputData inputData)
        {
            if (inputData.Down)
            {
                return this.HandleKeyDown(sceneInputData, inputData);
            }
            else
            {
                return this.HandleKeyUp(sceneInputData, inputData);
            }
        }

        /// <summary>
        /// Handle key down input data.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected virtual InputHandled HandleKeyDown(SceneToolInputData sceneInputData, KeyInputData inputData)
        {
            return InputHandled.No;
        }

        /// <summary>
        /// Handle key up input data.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected virtual InputHandled HandleKeyUp(SceneToolInputData sceneInputData, KeyInputData inputData)
        {
            return InputHandled.No;
        }

        /// <summary>
        /// Handle focus input data.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected virtual InputHandled HandleFocus(SceneToolInputData sceneInputData, FocusInputData inputData)
        {
            if (inputData.Focus)
            {
                return this.HandleGainedFocus(sceneInputData, inputData);
            }
            else
            {
                return this.HandleLostFocus(sceneInputData, inputData);
            }
        }

        /// <summary>
        /// Handle gained focus input data.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected virtual InputHandled HandleGainedFocus(SceneToolInputData sceneInputData, FocusInputData inputData)
        {
            return InputHandled.No;
        }

        /// <summary>
        /// Handle lost focus input data.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="inputData">Input data to handle.</param>
        /// <returns>Returns if the input data was handled or not.</returns>
        protected virtual InputHandled HandleLostFocus(SceneToolInputData sceneInputData, FocusInputData inputData)
        {
            return InputHandled.No;
        }

        #endregion

        #region Methods - Private - Message handlers

        /// <summary>
        /// Handle a scene selection changed message.
        /// </summary>
        /// <param name="msg">Selection changed message.</param>
        /// <returns>Returns whether the message was handled or not.</returns>
        private MessageHandlerResult HandleSceneSelectionChangedMessage(SceneSelectionChangedMessage msg)
        {
            this.Selection = msg.Selection;
            this.SceneSelectionChanged(this.Selection, msg.OldSelection);

            return MessageHandlerResult.Ignored;
        }

        /// <summary>
        /// Handle a scene cleared message.
        /// </summary>
        /// <param name="msg">Cleared message.</param>
        /// <returns>Returns whether the message was handled or not.</returns>
        private MessageHandlerResult HandleSceneClearedMessage(SceneClearedMessage msg)
        {
            this.Selection = new EntityCollection();
            this.SceneCleared();

            return MessageHandlerResult.Ignored;
        }

        #endregion
    }
}
